

local jass = require 'jass.common'
local japi = require 'jass.japi'
local japi2 = require 'util.japi2'
local lflag = require 'util.flag'
local dbg = require 'jass.debug'
local math = math
local setmetatable = setmetatable
local IssuePointOrderById             = jass.IssuePointOrderById
local IssueBuildOrderById             = jass.IssueBuildOrderById
local MoveLocation					  = jass.MoveLocation
local GetLocationZ					  = jass.GetLocationZ
local IsTerrainPathable				  = jass.IsTerrainPathable
local GetTerrainCliffLevel			  = jass.GetTerrainCliffLevel

local PATHING_TYPE_ANY                = 0
local PATHING_TYPE_WALKABILITY        = 1
local PATHING_TYPE_FLYABILITY         = 2
local PATHING_TYPE_BUILDABILITY       = 3
local PATHING_TYPE_PEONHARVESTPATHING = 4
local PATHING_TYPE_BLIGHTPATHING      = 5
local PATHING_TYPE_FLOATABILITY       = 6
local PATHING_TYPE_AMPHIBIOUSPATHING  = 7

---@class point "坐标点"
local point = {}
setmetatable(point, point)
ac.point = point 

-- 通行判定
point.mobility = {
	["可通行地面"] = 0x2,
    ["空中单位可通行"] = 0x4,
    ["可建造地面"] = 0x8,
    ["不是荒芜之地"] = 0x20,
    ["可通行海面"] = 0x40,
    ["两栖单位可通行"] = 0x80,
}

--- 输出点
function point:__tostring()
    return ('{%.4f, %.4f, %.4f}'):format(self:get())
end

--- 创建点
function point:__call(x, y, z)
	return setmetatable( {x = x or 0, y = y or 0, z = z or 0,} , point)
end

--- 获取移动器单位
function point:get_helper()
	if not point._helper or point._helper.removed then
		local unit = ac.player(14):create_unit('移动器', self)
		unit:add_restriction '幽灵'
		unit:add_restriction '相位'
		unit:add_restriction '无敌'
		unit:hide()
		point._helper = unit
	end
	return point._helper
end 

--- 模块初始化
function point.init()
    --- jass中的点
	point.handle = jass.Location(0, 0) 
	dbg.handle_ref(point.handle)
end

--- 移动点 按照直角坐标系移动(point + {x, y})
---	@params dest table
--- @return point 新点
function point:__add(dest)
	return ac.point(self.x + (dest[1] or 0), self.y + (dest[2] or 0),self.z + (dest[3] or 0))
end

--- 移动点 按照极坐标系移动(point - {angle,distance})
---	@params dest table
--- @return point 新点
function point:__sub(dest)
	local x, y = self:get()
	local angle, distance = (dest[1] or 0),(dest[2] or 0)
	return ac.point(x + distance * math.cos(angle), y + distance * math.sin(angle))
end

--- 获取两点间距 (point * point)
--- @param dest point 
--- @return number
function point:__mul(dest)
	local x1, y1 = self:get()
	local x2, y2 = dest:get()
	local x0, y0 = x1 - x2, y1 - y2
	return math.sqrt(x0 * x0 + y0 * y0)
end

--- 获取两点间方向 (point / point)
--- @param dest point 
--- @returns number
function point:__div(dest)
	local x1, y1 = self:get()
	local x2, y2 = dest:get()
	return math.atan(y2 - y1, x2 - x1)
end

--- 获取两点间仰俯角 (point ^ point)
--- @params dest point 
--- @return number
function point:__pow(dest)
    local x1 , y1 , z1 = self:get( )
    local x2 , y2 , z2 = dest:get( )
	return math.atan( z2-z1 , math.sqrt( (x2-x1)^2 + (y1-y2)^2 ) )
end

--- 获取两点间高度差 (point % point)
--- @params dest point 
--- @returns number
function point:__mod(dest)
	return math.abs(self.z - dest.z)
end

--- 判断两点坐标是否相同
--- @param dest point 
--- @returns boolean
function point:__eq(dest)
	local x1,y1,z1 = self:get()
	if dest and dest.type == 'point' then 
		local x2,y2,z2 = dest:get()
		return (x1==x2) and (y1==y2) and (z1==z2)
	end 
	return false 
end 

--- 获取x, y坐标的高度
function point.get_location_height(x, y)
	MoveLocation(point.handle, x, y)
	local z = GetLocationZ(point.handle)
	-- if IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then 
	-- 	z = 0
	-- end 
	return z
end

--- 获取单位的z
function point.get_unit_z(unit)
	local x,y = unit:get_point():get()
	return point.get_location_height(x, y) + unit:get_height()
end

--- 获取鼠标点
function point.get_mouse_point()
	return ac.point(japi.GetMouseX(), japi.GetMouseY())
end 

--结构
local mt = {}
point.__index = mt

--- 类型
mt.type = 'point'

--坐标x
mt.x = 0	

--坐标y
mt.y = 0	

--坐标z
mt.z = 0	

--- 获取点的x,y,z
function mt:get()
	return self.x,self.y,self.z
end

function mt:getZ()
	return self.z
end

--- 兼容性通用接口
function mt:get_point()
	return self 
end 

--- 拷贝
function mt:copy()
	return ac.point(self:get() )
end 

--- 获取碰撞
function mt:get_collision()
	return 0
end

--移动点
function mt:move(x, y, z)
	self.x = x
	self.y = y
	self.z = z
	return self 
end

--- 获得地层级别
function mt:get_level()
	return GetTerrainCliffLevel(self:get()) 
end

--- 获取地层高度
function mt:get_terrain()
    return (self:get_level() - 2) * 128
end

--- 获取点的地面高度
function mt:get_height()
	return point.get_location_height(self.x, self.y)
end

-- constant pathingtype PATHING_TYPE_ANY = ConvertPathingType(0)
-- constant pathingtype PATHING_TYPE_WALKABILITY = ConvertPathingType(1)
-- constant pathingtype PATHING_TYPE_FLYABILITY = ConvertPathingType(2)
-- constant pathingtype PATHING_TYPE_BUILDABILITY = ConvertPathingType(3)
-- constant pathingtype PATHING_TYPE_PEONHARVESTPATHING = ConvertPathingType(4)
-- constant pathingtype PATHING_TYPE_BLIGHTPATHING = ConvertPathingType(5)
-- constant pathingtype PATHING_TYPE_FLOATABILITY = ConvertPathingType(6)
-- constant pathingtype PATHING_TYPE_AMPHIBIOUSPATHING = ConvertPathingType(7)

--- 是否可通行
function mt:is_flyable()
	local x,y = self:get()
	if IsTerrainPathable(x,y, PATHING_TYPE_WALKABILITY) then 
		return false 
	end
	if IsTerrainPathable(x,y, PATHING_TYPE_FLYABILITY) then 
		return false 
	end
	return true
end

function mt:is_mobility(flags)
	local x,y = self:get()
	return japi2.LocationMobility(x, y, lflag.tobit(flags or "可通行地面", point.mobility))
end 

-- 在附近寻找一个可通行的点
--	[采样范围]
--	[初始角度]
--	[不包含当前位置]
function mt:find_moveable_point(r, angle, other)
    local r = r or 512
    local angle = angle or 0
    local x0, y0 = self:get()
    if not other and self:is_flyable() then return self end

    for r = math.min(r, 32), r, 32 do
        for angle = angle, angle + 315, 45 do
            local p = self - {angle, r}
            if p:is_flyable() then return p end
        end
    end
end
-- --- 是否可通行（strict = true 严格检查这个点，不能异步使用）
-- --- 检查最大范围值

point.CHECK_MAX_RANGE = 128.0

function mt:is_walkable(strict)
	local x,y = self:get()
	if IsTerrainPathable(x,y, PATHING_TYPE_WALKABILITY) then 
		return false 
	end
	if not strict then 
		return true 
	end 
	local group = { }
	for unit in ac.selector("unit")
		:range(self, point.CHECK_MAX_RANGE)
		:can_god()
		:not_build()
		:filter(function(u)
			return not(u:has_restriction '幽灵')
		end)
		:loop()
	do
		unit:add_restriction '幽灵'
		table.insert(group, unit)
	end
	local flag = self:is_mobility("可通行地面")
	for _, unit in ipairs(group) do
		unit:remove_restriction '幽灵'
	end
	return flag
end

-- 点范围内随机点
function mt:random(area,angle)
    local area = (1 - math.sqrt(math.random(100*100))/100) * area
    local angle = angle or math.random(360)
    local p = self - {angle,area}
    return p
end 

function mt:is_walkable2(strict)
	local x,y = self:get()
	if IsTerrainPathable(x,y, PATHING_TYPE_WALKABILITY) then 
		return false 
	end
	if not strict then 
		return true 
	end 
	local mover = point:get_helper() 
	local group = { }
	for unit in ac.selector("unit")
		:range(self, point.CHECK_MAX_RANGE)
		:can_god()
		:not_build()
		:filter(function(u)
			return not(u:has_restriction '幽灵')
		end)
		:loop()
	do
		unit:add_restriction '幽灵'
		table.insert(group, unit)
	end
	local flag = IssueBuildOrderById(mover.handle, mover._id, x, y)
	for _, unit in ipairs(group) do
		unit:remove_restriction '幽灵'
	end
	return flag
end

-- function mt:is_walkable(strict)
-- 	local x,y = self:get()
-- 	if IsTerrainPathable(x,y, PATHING_TYPE_WALKABILITY) then 
-- 		return false 
-- 	end
-- 	if not strict then 
-- 		return true 
-- 	end 
-- 	if not point.mover or point.mover.removed then
-- 		point.mover = self:add_item("移动器")
-- 	end
-- 	local items = {}
-- 	-- 隐藏该区域中的任何项目以避免与我们的项目冲突
-- 	local check = ac.rect.from(self, 128.0, 128.0)
-- 	for item in ac.selector("item"):rect(check):loop()	do 
-- 		if item:is_visible() then 
-- 			item:hide()
-- 			items[#items+1] = item
-- 		end
-- 	end 
-- 	-- 尝试移动测试项并获取其坐标
-- 	point.mover:show()
-- 	point.mover:set_point(self)
-- 	local real = point.mover:get_point()
-- 	-- 再藏起来
-- 	point.mover:hide()
-- 	-- 还原隐藏的物品
-- 	for i = 1,#items do 
-- 		items[i]:show()
-- 	end 
-- 	return (self * real) <= point.CHECK_MAX_RANGE
-- end

--- 是否可建造
function mt:is_buildable()
	local x,y = self:get()
	if IsTerrainPathable(x,y, PATHING_TYPE_BUILDABILITY) then 
		return false 
	end
	return true
end

-- 地形为水
function mt:is_water()
	return self:is_deepwater() or self:is_shallowwater()
end

-- 地形为深水
function mt:is_deepwater()
	local x,y = self:get()
    return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
end

-- 地形为浅水
function mt:is_shallowwater()
	local x,y = self:get()
    return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
end

-- 地形为陆地
function mt:is_land()
	local x,y = self:get()
    return IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
end

-- 地形为平台
function mt:is_platform()
	local x,y = self:get()
    return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
end

--获取弹道命中z偏移
function mt:get_impactZ()
    return 0
end 

--获取弹道命中点
function mt:get_impact_point()
    local impact = self:copy()
    impact.z = impact:get_height() 
    return impact
end 

--- 计算碰撞后的镜面反射角
--- @params angle 当前角度
--- @params amove 移动距离
--- @returns angle 折射角
function mt:of_reflex(angle,amove)
    local block = self + {amove * math.cos(angle),0}
    if (not block:is_walkable()) or (block:get_height() > self:get_height()) then
        return 180 - angle
    else
        local block = self + {0,amove * math.sin(angle)}
        if (not block:is_walkable()) or (block:get_height() > self:get_height()) then
            return - angle
        end
    end
    return angle
end

--- 插值点
function mt:lerp(dest,t)
    return ac.point(
		math.lerp(self.x,dest.x,t),
		math.lerp(self.y,dest.y,t),
		math.lerp(self.z,dest.z,t)
	)
end

--- 点是否可见
function mt:is_visible(player)
	return jass.IsVisibleToPlayer(self.x, self.y, player.handle)
end 


--  在附近寻找一个可用于召唤地面单位的点 
--	[采样范围]
--	[初始角度]
--	[不包含当前位置]
function mt:find_summon_point(r, angle, other)
	local r = r or 512
	local angle = angle or 0
	local x0, y0 = self:get()
	local mover = point:get_helper() 
	if not other and IssuePointOrderById(mover.handle, 0xD02B2, x0, y0) then
		return self
	end
	for r = math.min(r, 32), r, 32 do
		for a = angle, angle + 315, 45 do
			local p = self - {a, r}
			if IssuePointOrderById(mover.handle, 0xD02B2, p.x, p.y) then
				return p
			end
		end
	end
	return self
end

return point